﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Linguist.Dictionary;
using System.Diagnostics;
using Common;

namespace Linguist
{
    /// <summary>
    /// This class can be used to keep track of a word sequence. This class is an
    /// immutable class. It can never be modified once it is created (except,
    /// perhaps for transient, cached things such as a precalculated hashcode).
    /// </summary>
    public class WordSequence : IComparable<WordSequence>, IWordSequence
    {
        int IComparable<WordSequence>.CompareTo(WordSequence other)
        {
            return getOldest().compareTo(other.getOldest()); 
        }

        /// <summary>
        /// an empty word sequence, that is, it has no words.
        /// </summary>
        public static WordSequence EMPTY = new WordSequence(0);

        public static IWordSequence asWordSequence(IDictionary dictionary,String[] words) 
        {
            IWord[] dictWords = new Word[words.Length];
            for (int i = 0; i < words.Length; i++) 
            {
                dictWords[i] = dictionary.getWord(words[i]);
            }
            return new WordSequence(dictWords);
        }

        private IWord[] words=null;
        private int hashCodeVal = -1;

        /// <summary>
        /// Constructs a word sequence with the given depth. 
        /// </summary>
        /// <param name="size">the maximum depth of the word history</param>
        private WordSequence(int size) 
        {
            words = new Word[size];
        }
        /// <summary>
        /// Constructs a word sequence with the given word IDs
        /// </summary>
        /// <param name="words">the word IDs of the word sequence</param>
        public WordSequence(IWord[] words):
            this(words.ToList<IWord>())
        {
            
        }
        /// <summary>
        /// Constructs a word sequence from the list of words
        /// </summary>
        /// <param name="list">the list of words</param>
        public WordSequence(List<IWord> list) 
        {
            this.words = list.ToArray<IWord>();
            check();
        }

        public void check() 
        {
            foreach(IWord word in words)
                if (word == null)
                    throw new Exception("WordSequence should not have null Words.");
        }
        /// <summary>
        /// Returns a new word sequence with the given word added to the sequence 
        /// </summary>
        /// <param name="word">the word to add to the sequence</param>
        /// <param name="maxSize">the maximum size of the generated sequence</param>
        /// <returns>a new word sequence with the word added (but trimmed to maxSize)</returns>
        public IWordSequence addWord(IWord word, int maxSize) 
        {
            if (maxSize <= 0) 
            {
                return EMPTY;
            }
            int nextSize = ((size() + 1) > maxSize) ? maxSize : (size() + 1);
            WordSequence next = new WordSequence(nextSize);
            int nextIndex = nextSize - 1;
            int thisIndex = size() - 1;
            next.words[nextIndex--] = word;

            while (nextIndex >= 0 && thisIndex >= 0) {
                next.words[nextIndex--] = this.words[thisIndex--];
            }
            next.check();

            return next;
        }
        /// <summary>
        /// Returns the oldest words in the sequence (the newest word is omitted) 
        /// </summary>
        /// <returns>the oldest words in the sequence, with the newest word omitted</returns>
        public IWordSequence getOldest() 
        {
            WordSequence next = EMPTY;

            if (size() >= 1) 
            {
                next = new WordSequence(words.Length - 1);
                this.words.CopyTo(next.words,0);
                ///System.arraycopy(this.words, 0, next.words, 0, next.words.Length);
            }
            return next;
        }
        /// <summary>
        /// Returns the newest words in the sequence (the old word is omitted)
        /// </summary>
        /// <returns>the newest words in the sequence with the oldest word omitted</returns>
        public IWordSequence getNewest() 
        {
            WordSequence next = EMPTY;

            if (size() >= 1) 
            {
                next = new WordSequence(words.Length - 1);
                this.words.CopyTo(next.words,1);
                //System.arraycopy(this.words, 1, next.words, 0, next.words.length);
            }
            return next;
        }

        /// <summary>
        /// Returns a word sequence that is no longer than the given size, that is
        /// filled in with the newest words from this sequence 
        /// </summary>
        /// <param name="maxSize">the maximum size of the sequence</param>
        /// <returns>a new word sequence, trimmed to maxSize.</returns>
        public IWordSequence trim(int maxSize) 
        {
            if (maxSize <= 0 || size() == 0) 
            {
                return EMPTY;
            } 
            else if (maxSize == size()) 
            {
                return this;
            } 
            else 
            {
                if (maxSize > size()) 
                {
                    maxSize = size();
                }
                WordSequence next = new WordSequence(maxSize);
                int thisIndex = words.Length - 1;
                int nextIndex = next.words.Length - 1;

                for (int i = 0; i < maxSize; i++) 
                {
                    next.words[nextIndex--] = this.words[thisIndex--];
                }
                return next;
            }
        }
        /// <summary>
        /// Returns the n-th word in this sequence
        /// </summary>
        /// <param name="n">which word to return</param>
        /// <returns>the n-th word in this sequence</returns>
        public IWord getWord(int n) 
        {
            Trace.Assert( n < words.Length);
            return words[n];
        }
        /// <summary>
        /// Returns the number of words in this sequence
        /// </summary>
        /// <returns></returns>
        public int size() 
        {
            return words.Length;
        }
        /// <summary>
        /// Returns a string representation of this word sequence. The format is:
        /// [ID_0][ID_1][ID_2].
        /// </summary>
        /// <returns></returns>
        override public String ToString() 
        {
            StringBuilder sb = new StringBuilder();
            foreach (Word word in words)
                sb.Append('[').Append(word).Append(']');
            return sb.ToString();
        }

        /// <summary>
        /// Calculates the hashcode for this object
        /// </summary>
        /// <returns></returns>
        public int hashCode() 
        {
            if (hashCodeVal == -1) 
            {
                int code = 123;
                for (int i = 0; i < words.Length; i++) {
                    code += words[i].GetHashCode()* (2* i + 1);
                }
                hashCodeVal = code;
            }
            return hashCodeVal;
        }
        /// <summary>
        /// compares the given object to see if it is identical to this WordSequence 
        /// </summary>
        /// <param name="objectparam">the object to compare this to</param>
        /// <returns>true if the given object is equal to this object</returns>
        public Boolean equals(Object objectparam) 
        {
            if (this == objectparam)
                return true;
            if (!(objectparam is WordSequence))
                return false;

            return Array.Equals(words, ((WordSequence) objectparam).words);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="startIndex"></param>
        /// <param name="stopIndex"></param>
        /// <returns>a subsequence with both <code>startIndex</code> and  <code>stopIndex</code> exclusive.</returns>
        public IWordSequence getSubSequence(int startIndex, int stopIndex) 
        {
            List<IWord> subseqWords = new List<IWord>();

            for (int i = startIndex; i < stopIndex; i++) {
                subseqWords.Add(getWord(i));
            }

            return new WordSequence(subseqWords);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns>the words of the <code>WordSequence</code>.</returns>
        public IWord[] getWords() 
        {
            return getSubSequence(0, size()).getWords(); // create a copy to keep the
                                                    // class immutable
        }

        public int compareTo(IWordSequence other) 
        {
            int n = Math.Min(words.Length, other.getWords().Length);
            for (int i = 0; i < n; ++i) 
            {
                if (!words[i].Equals(other.getWords()[i])) 
                {
                    return words[i].compareTo(other.getWords()[i]);
                }
            }

            return words.Length - other.getWords().Length;
        }

    }
}
